package cn.liumouren.jzxd.utils;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.IService;
import com.sun.istack.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.time.Duration;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * @author Daniel Liu
 * @date 2021/1/31 22:28
 */
@Component
public class RedisUtil {

    private static RedisTemplate<String, Object> redisTemplate;

    @Autowired
    public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
        RedisUtil.redisTemplate = redisTemplate;
    }

    public static void set(String key, Object value, Duration ttl) {
        redisTemplate.opsForValue().set(key, value, ttl);
    }

    public static void set(String key, Object value) {
        redisTemplate.opsForValue().set(key, value);
    }

    /**
     * 增加或者减少对象属性值
     *
     * @param key    key
     * @param prop   要增加或者减少的属性
     * @param offset 1:表示+1 ; -1:表示-1
     * @param <T>    实体对象
     */
    public static <T> void offset(String key, String prop, int offset) {
        T t = get(key);
        if (t != null) {
            try {
                Class<?> clazz = t.getClass();
                Method getProp = clazz.getDeclaredMethod("get" + StrUtil.upperFirst(prop));
                Integer c = (Integer) getProp.invoke(t);
                c = c + offset;
                Method setProp = clazz.getDeclaredMethod("set" + StrUtil.upperFirst(prop), Integer.class);
                setProp.invoke(t, c);
            } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
                e.printStackTrace();
            }
            set(key, t, Duration.ofHours(25));
        }
    }

    @SuppressWarnings("unchecked")
    public static <T> T get(String key) {
        return (T) redisTemplate.opsForValue().get(key);
    }

    /**
     * 先从redis中找, 找不到从数据库找,
     * 数据库中没有返回 null,
     * 数据库里有存入redis并返回
     *
     * @param key
     * @return
     */
    public static <T> T getAndSet(String key, IService<T> service, @NotNull Class<T> clazz) {
        T t = get(key);
        if (t == null) {
            // redis里没有, 找数据库
            int start = key.lastIndexOf(":") + 1;
            String xxxId = key.substring(start);
            String simpleName = clazz.getSimpleName();
            T one = service.getOne(new QueryWrapper<T>().eq(StrUtil.toUnderlineCase(simpleName + "Id"), xxxId));
            if (one == null) {
                return null;
            }
            // 存入redis
            set(StrUtil.format("entity:{}:{}", StrUtil.lowerFirst(simpleName), xxxId), one, Duration.ofHours(25));
            return one;
        }
        return t;
    }

    @SuppressWarnings("unchecked")
    public static <T> T get(String key, Class<T> clazz) {
        return (T) redisTemplate.opsForValue().get(key);
    }

    /**
     * 根据re获取所有满足的key
     *
     * @param re
     * @return
     */
    public static Set<String> keys(String re) {
        return redisTemplate.keys(re);
    }

    public static <T> List<T> batchGet(String pre, Class<T> clazz) {
        Set<String> keys = redisTemplate.keys(pre.concat("*"));
        if (keys == null) {
            return null;
        }
        @SuppressWarnings("unchecked")
        List<T> objects = (List<T>) redisTemplate.opsForValue().multiGet(keys);
        return objects;
    }

    public static void remove(String key) {
        redisTemplate.delete(key);
    }

    public static void remove(Collection<String> keys) {
        redisTemplate.delete(keys);
    }

    public static void removeByRe(String re) {
        remove(keys(re));
    }

    public static <T> T getAndRemove(String key, Class<T> clazz) {
        T t = get(key, clazz);
        remove(key);
        return t;
    }

    public static void removeByPre(String key) {
        Set<String> keys = redisTemplate.keys(key.concat("*"));
        if (!CollectionUtil.isEmpty(keys)) {
            redisTemplate.delete(keys);
        }
    }

    public static void leftPush(String key, Object value) {
        redisTemplate.opsForList().leftPush(key, value);
    }

    /**
     * 如果key存在直接push,
     * 如果key不存在, 则给key设置过期时间
     *
     * @param key
     * @param value
     * @param timeout
     * @param timeUnit
     */
    public static void leftPush(String key, Object value, long timeout, TimeUnit timeUnit) {
        if (CollectionUtil.isEmpty(get(key, List.class))) {
            // 说明还没有该 key
            leftPush(key, value);
            // 设置过期时间
            redisTemplate.expire(key, timeout, timeUnit);
        } else {
            leftPush(key, value);
        }
    }

    /**
     * 监听发送短信队列
     */
    @SuppressWarnings("unchecked")
    public static void listenSendSms() {
        // 最大重试次数
        final int maxRetry = 3;
        while (true) {
            // 挂起等待30秒
            Object o = redisTemplate.opsForList().rightPop("sendSms", 45, TimeUnit.SECONDS);
            if (o != null) {
                Map<String, Object> map = null;
                if (o instanceof Map) {
                    map = (Map<String, Object>) o;
                }
                if (map != null) {
                    String phone = (String) map.get("phone");
                    String code = (String) map.get("code");
                    try {
                        // 发送短信...
                        AliSmsUtil.sendSms(phone, code);
                    } catch (Exception e) {
                        // 发送失败重新加入队列
                        Integer retryTimes = (Integer) map.get("retryTimes");
                        if (retryTimes == null) {
                            map.put("retryTimes", 1);
                            leftPush("sendSms", map);
                        }
                        if (retryTimes != null && retryTimes <= maxRetry) {
                            // 标记重试次数
                            map.put("retryTimes", retryTimes + 1);
                            leftPush("sendSms", map);
                        }
                    }
                }
            }
        }
    }
}
